<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <meta charset="UTF-8"/>
  <meta name="viewport" content="width=device-width, initial-scale=1"/>
  <title>bpkg-repository-signing(1) bpkg 0.17.0</title>

  <style type="text/css">
/* file      : common.css
 * license   : MIT; see accompanying LICENSE file
 */

html
{
  font-family: "Helvetica Neue", Helvetica, "Segoe UI", Arial, freesans, sans-serif;
  font-weight: normal;
  font-size: 18px;
  line-height: 1.4em;
  letter-spacing: 0.01em;

  color: #292929;
}

body {margin: 0;} /* There is non-0 default margin for body. */

/* See notes on what's going on here. */
body {min-width: 17em;}
@media only screen and (min-width: 360px)
{
  body {min-width: 19em;}
}

/*
 * Header (optional).
 */

#header-bar
{
  width: 100%;

  background: rgba(0, 0, 0, 0.04);
  border-bottom: 1px solid rgba(0, 0, 0, 0.2);

  padding: .4em 0 .42em 0;
  margin: 0 0 1.4em 0;
}

#header
{
  /* Same as in #content. */
  max-width: 41em;
  margin: 0 auto 0 auto;
  padding: 0 .4em 0 .4em;

  -webkit-box-sizing: border-box;
  -moz-box-sizing: border-box;
  box-sizing: border-box;

  width: 100%;
  display: table;
  border: none;
  border-collapse: collapse;
}

#header-logo, #header-menu
{
  display: table-cell;
  border: none;
  padding: 0;
  vertical-align: middle;
}

#header-logo {text-align: left;}
#header-menu {text-align: right;}

/* These overlap with #header's margin because of border collapsing. */
#header-logo {padding-left: .4em;}
#header-menu {padding-right: .4em;}

#header-logo a
{
  color: #000;
  text-decoration: none;
  outline: none;
}
#header-logo a:visited {color: #000;}
#header-logo a:hover, #header-logo a:active {color: #000;}

#header-menu a
{
  font-size: 0.889em;
  line-height: 1.4em;
  text-align: right;
  margin-left: 1.2em;
  white-space: nowrap;
  letter-spacing: 0;
}

#header-menu a
{
  color: #000;
  outline: none;
}
#header-menu a:visited {color: #000;}
#header-menu a:hover, #header-menu a:active
{
  color: #3870c0;
  text-decoration: none;
}

/* Flexbox-based improvements though the above works reasonably well. */
#header-menu-body
{
  width: 100%;

  display: -webkit-inline-flex;
  display: inline-flex;

  -webkit-flex-flow: row wrap;
  flex-flow: row wrap;

  -webkit-justify-content: flex-end;
  justify-content: flex-end;
}

/* Whether we want it (and at which point) depends on the size of the menu. */
/*
@media only screen and (max-width: 567px)
{
  #header-menu-body
  {
    -webkit-flex-direction: column;
    flex-direction: column;
  }
}
*/

/*
 * Content.
 */

#content
{
  max-width: 41em;
  margin: 0 auto 0 auto;
  padding: 0 .4em 0 .4em; /* Space between text and browser frame. */

  -webkit-box-sizing: border-box;
  -moz-box-sizing: border-box;
  box-sizing: border-box;
}

/*
 * Footer (optional).
 */

#footer
{
  color: #767676;
  font-size: 0.7223em;
  line-height: 1.3em;
  margin: 2.2em 0 1em 0;
  text-align: center;
}

#footer a
{
  color: #767676;
  text-decoration: underline;
}
#footer a:visited {color: #767676;}
#footer a:hover, #footer a:active {color: #3870c0;}

/* Screen size indicator in the footer. The before/after content is in case
   we don't have any content in the footer. Margin is to actually see the
   border separate from the browser frame. */

/*
#footer:before {content: "\A0";}
#footer:after {content: "\A0";}

#footer
{
  border-left: 1px solid;
  border-right: 1px solid;
  margin-left: 1px;
  margin-right: 1px;
}

@media only screen and (max-width: 359px)
{
  #footer {border-color: red;}
}

@media only screen and (min-width: 360px) and (max-width: 567px)
{
  #footer {border-color: orange;}
}

@media only screen and (min-width: 568px) and (max-width: 1023px)
{
  #footer {border-color: blue;}
}

@media only screen and (min-width: 1024px)
{
  #footer {border-color: green;}
}
*/

/*
 * Common elements.
 */

p, li, dd {text-align: justify;}
.code {text-align: left;} /* Manually aligned. */
pre {text-align: left;}   /* If it is inside li/dd. */

/* Notes. */

.note
{
  color: #606060;
}

div.note
{
  margin: 2em 0 2em 0; /* The same top/bottom margings as pre box. */

  padding-left: 0.5em;
  border: 0.25em;
  border-left-style: solid;
  border-color: #808080;

  page-break-inside: avoid;
}

div.note :first-child {margin-top:    0;}
div.note :last-child  {margin-bottom: 0;}

span.note::before {content: "[Note: "}
span.note::after  {content: "]"}

/* Links. */
a
{
  color: #3870c0;
  /*color: #4078c0;*/
  text-decoration: none;
}

a:hover, a:active
{
/*color: #006fbf;*/
/*color: #0087e7;*/
  text-decoration: underline;
}

a:visited
{
/*color: #003388;*/
  color: #00409c;
}

/* Standard paragraph. */

p, pre {margin: 1em 0 1em 0;}

/* Standard lists. */
ul, ol, dl {margin: 1em 0 1em 0;}
ul li, ol li {margin: 0 0 .4em 0;}
ul li {list-style-type: circle;}
dl dt {margin: 0 0 0 0;}
dl dd {margin: 0 0 .6em 1.8em;}

code, pre
{
  font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace;
  font-size: 0.92em;
  letter-spacing: 0;
}

pre {white-space: pre-wrap;}
@media only screen and (max-width: 567px)
{
  pre {word-break: break-all;}
}

/* Use page rather than system font settings. */
input
{
  font-family: inherit;
  font-weight: inherit;
  font-size:   inherit;
  line-height: inherit;
}

/* file      : pre-box.css
 * license   : MIT; see accompanying LICENSE file
 */

/* Note: see also p-code-box.css. */

pre
{
  background-color: rgba(0, 0, 0, 0.05);
  border-radius: 0.2em;
  padding: .8em .4em .8em .4em;
  margin: 2em -.4em 2em -.4em; /* Use margins of #content. */
}

/* file      : man.css
 * license   : MIT; see accompanying LICENSE file
 */

/* Bases:
 *
 * common.css
 * pre-box.css
 *
 */

#content
{
  max-width: 42.1em;
  padding-left: 1.5em; /* Reserve for the heading. */
}

h1
{
  font-weight: normal;
  font-size: 1.58em;
  line-height: 1.4em;
  margin: 1.6em 0 .6em -.88em;
}

/* Definition list for options. */
dl.options dt {margin: 1em 0 0 0;}
dl.options dd {margin: .1em 0 0 4.5em;}

/* Make lists inside option descriptions a tad smaller. */
dl.options dd ul, dl.options dd ol, dl.options dd dl
{
  font-size: 0.889em;
  line-height: 1.4em;
}

  </style>

</head>
<body>
<div id="content">

  <h1>NAME</h1>

  <p><b><code>bpkg-repository-signing</code></b> &#8211; how to sign repository</p>
  <h1>SYNOPSIS</h1>

  <p class="code"><code><b>bpkg rep-create --key</b> ...</code></p>

  <h1>DESCRIPTION</h1>

  <p>The purpose of signing a repository is to prevent tampering with packages
  either during transmission or on the repository <i>host machine</i>.
  Ideally, you would generate and sign the repository manifests on a separate
  <i>build machine</i> that is behind a firewall. This way, if (or, really,
  when) your host machine is compromised, it will be difficult for an attacker
  to compromise the repository packages without being noticed. Since the
  repository key is kept on the build machine (or, better yet, on a
  <i>one-way</i> PIV/PKCS#11 device; see below) they will not be able to
  re-sign the modified repository.</p>

  <p><code><b>bpkg</b></code> uses X.509 public key cryptography for
  repository signing. Currently, only the explicit <i>first use</i>
  certificate authentication is implemented.  That is, for an unknown (to this
  <code><b>bpkg</b></code> configuration) repository certificate its subject
  information and fingerprint are presented to the user. If the user confirms
  the authenticity of the certificate, then it is added to the configuration
  and any repository that in the future presents this certificate is trusted
  without further confirmations, provided its name matches the certificate's
  subject (see below). In the future a certificate authority (CA)-based model
  may be added.</p>

  <p>The rest of this guide shows how to create a key/certificate pair for
  <code><b>pkg</b></code> repository signing and use it to sign a repository.
  At the end it also briefly explains how to store the private key on a
  PIV/PKCS#11 device using Yubikey 4 as an example.</p>

  <dl>
  <dt style="margin-bottom:1em">1. Generate Private Key</dt>
  <dd>The first step is to generate the private key:

  <pre>$ openssl genrsa -aes256 2048 >key.pem</pre>

  <p>If you would like to generate a key without password protection (not a
  good idea except for testing), leave the <code><b>-aes256</b></code> option
  out. You may also need to add <code><b>-nodes</b></code> depending on your
  <code><b>openssl(1)</b></code> configuration.</p></dd>

  <dt style="margin-top:1em;margin-bottom:1em">2. Generate Certificate</dt>
  <dd>Next create the certificate configuration file by saving the following
  into <code><b>cert.conf</b></code>. You may want to keep it around in case
  you need to renew an expired certificate, etc.

  <pre>name  = example.com
org   = Example, Inc
email = admin@example.com

[req]
distinguished_name = req_distinguished_name
x509_extensions    = v3_req
prompt             = no
utf8               = yes

[req_distinguished_name]
O  = $org
CN = name:$name

[v3_req]
keyUsage         = critical,digitalSignature
extendedKeyUsage = critical,codeSigning
subjectAltName   = email:$email</pre>

  <p>Adjust the first three lines to match your details. If the repository is
  hosted by an organization, use the organization's name for
  <code><b>org</b></code>. If you host it as an individual, put your full,
  real name there. Using any kind of aliases or nicknames is a bad idea
  (except, again, for testing). Remember, users of your repository will be
  presented with this information and if they see it was signed by someone
  named SmellySnook, they will unlikely trust it. Also use a working email
  address in case users need to contact you about issues with your
  certificate. Note that the <code><b>name:</b></code> prefix in the
  <code><b>CN</b></code> value is not a typo.</p>

  <p>The <code><b>name</b></code> field is a canonical repository name prefix
  with the <code><b>pkg:</b></code> type part stripped. Any repository with a
  canonical name that starts with this prefix can be authenticated by this
  certificate (see the repository manifest documentation for more information
  on canonical names). For example, name <code><b>example.com</b></code> will
  match any repository hosted on
  <code><b>{,www.,pkg.,bpkg.}example.com</b></code>. While name
  <code><b>example.com/math</b></code> will match
  <code><b>{...}example.com/pkg/1/math</b></code> but not
  <code><b>{...}example.com/pkg/1/misc</b></code>.</p>

  <p>A certificate name can also contain a subdomain wildcard. A wildcard name
  in the <code><b>*.example.com</b></code> form matches any single-level
  subdomain, for example <code><b>foo.example.com</b></code> but not
  <code><b>foo.bar.example.com</b></code> while a wildcard name in the
  <code><b>**.example.com</b></code> form matches any subdomain, including
  multi-level. The above two forms do not match the domain itself
  (<code><b>example.com</b></code> in the above example). If this is desired,
  the <code><b>*example.com</b></code> and <code><b>**example.com</b></code>
  forms should be used instead. Note that these forms still only match
  subdomains. In other words, they won't match
  <code><b>fooexample.com</b></code>. Wildcard names are less secure and
  therefore are normally only used for testing and/or internal
  repositories.</p>

  <p>Once the configuration file is ready, generate the certificate:</p>

  <pre>openssl req -x509 -new -sha256 -key key.pem \
  -config cert.conf -days 730 >cert.pem</pre>

  <p>To verify the certificate information, run:</p>

  <pre>openssl x509 -noout -nameopt RFC2253,sep_multiline \
  -subject -dates -email &lt;cert.pem</pre></dd>

  <dt style="margin-top:1em;margin-bottom:1em">3. Add Certificate to
  Repository</dt>
  <dd>Add the <code><b>certificate:</b></code> field for the base repository
  (<code><b>role: base</b></code>) in the <code><b>repositories</b></code>
  manifest file(s):

  <pre>certificate:
\
&lt;cert>
\</pre>

  <p>Replace <i>cert</i> with the entire contents of
  <code><b>cert.pem</b></code> (including the <code>BEGIN CERTIFICATE</code>
  and <code>END CERTIFICATE</code> lines). So you will have an entry like
  this:</p>

  <pre>certificate:
\
-----BEGIN CERTIFICATE-----
MIIDQjCCAiqgAwIBAgIJAIUgsIqSnesGMA0GCSqGSIb3DQEBCwUAMDkxFzAVBgNV
.
.
.
+NOVBamEvjn58ZcLfWh2oKee7ulIZg==
-----END CERTIFICATE-----
\</pre></dd>

  <dt style="margin-top:1em;margin-bottom:1em">4. Sign Repository</dt>
  <dd>When generating the repository manifests with the <a
  href="bpkg-rep-create.xhtml"><code><b>bpkg-rep-create(1)</b></code></a>
  command, specify the path to <code><b>key.pem</b></code> with the
  <code><b>--key</b></code> option:

  <pre>bpkg rep-create --key /path/to/key.pem /path/to/repository</pre>

  <p>You will be prompted for a password to unlock the private key.</p></dd>

  <dt style="margin-top:1em;margin-bottom:1em">5. Using PIV/PKCS#11
  Device</dt>
  <dd>This optional step shows how to load the private key into Yubikey 4 and
  then use it instead of the private key itself for signing the repository.
  Note that you will need OpenSSL 1.0.2 or later for the signing part to work.

  <p>First change the Yubikey MKEY, PUK, and PIN if necessary. You should
  definitely do this if it still has the factory defaults. Then import the
  private key and the certificate into Yubikey (replace <i>mkey</i> with the
  management key):</p>

  <pre>yubico-piv-tool --key=&lt;mkey> -a import-key -s 9c &lt;key.pem
yubico-piv-tool --key=&lt;mkey> -a import-certificate -s 9c &lt;cert.pem</pre>

  <p>After this you will normally save the certificate/private key onto backup
  media, store it in a secure, offline location, and remove the key from the
  build machine.</p>

  <p>To sign the repository with Yubikey specify the following options instead
  of just <code><b>--key</b></code> as at step 4 (<code>"SIGN key"</code> is
  the label for the slot <code>9c</code> private key):</p>

  <pre>bpkg rep-create                                                     \
  --openssl-option pkeyutl:-engine --openssl-option pkeyutl:pkcs11  \
  --openssl-option pkeyutl:-keyform --openssl-option pkeyutl:engine \
  --key "pkcs11:object=SIGN%20key" /path/to/repository</pre>

  <p>Note that for <code><b>openssl</b></code> versions prior to
  <code><b>3.0.0</b></code> <code><b>bpkg</b></code> uses the
  <code><b>rsautl</b></code> command instead of <code><b>pkeyutl</b></code>
  for the data signing operation.</p></dd>
  </dl>

  <h1>BUGS</h1>

  <p>Send bug reports to the
  <a href="mailto:users@build2.org">users@build2.org</a> mailing list.</p>

</div>

<div id="footer">
Copyright &#169; 2014-2024 the build2 authors.<br/>
Permission is granted to copy, distribute and/or modify this document under
the terms of the MIT License.
</div>

</body>
</html>
